package metricmonitor

import (
	"fmt"
	"github.com/beego/beego/v2/client/orm"
	ecsmodel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2/model"
	"scase.io/application-auto-scaling-service/pkg/cloudresource"
	"scase.io/application-auto-scaling-service/pkg/common"
	"scase.io/application-auto-scaling-service/pkg/db"
	"scase.io/application-auto-scaling-service/pkg/metricmonitor/influxdb"
	"scase.io/application-auto-scaling-service/pkg/service/taskservice"
	"scase.io/application-auto-scaling-service/pkg/utils/logger"
	"sync"
	"time"
)

const (
	defaultDeletingVmNum = 0
)

// 新增实例到数据库，进程层面的实例数量更新
// scaling group 的curNum 只通过该函数写入，以降低并发冲突
func GetActiveInstanceFromInfluxDB(log *logger.FMLogger) error {
	influxCtl, err := influxdb.NewController()
	if err != nil {
		log.Error("[instance_monitor] get influx controller error:%+v", err)
		return err
	}
	instances, err := influxCtl.GetInstance(log)
	influxCtl.CloseController()
	if err != nil {
		log.Error("[instance_monitor] get instance from influxDB error:%+v", err)
		return err
	}
	wg := sync.WaitGroup{}
	wg.Add(len(instances))
	for idx, instance := range instances {
		if idx > 0 && idx%common.InstanceCountToSleep == 0 {
			time.Sleep(common.InstanceCountToSleepSeconds * time.Second)
		}
		go func(instance influxdb.InstanceInfo) {
			defer wg.Done()
			// 如果已经处于deleting，即使接收到上报信号也不会更新状态，等到deleted之后，就不会接收到上报信息，更新实例数时自然会减少
			if db.EcsInfoTable().CheckExist(instance.InstanceId) {
				updateInstanceState(instance, log)
				return
			} else if db.IsPodExit(instance.InstanceId) {
				updatePodInstanceState(instance, log)
				return
			}
			// 2. 实例不存在，不操作
			log.Error("[instance_monitor]  instance [%s], fleetId [%s], scaling group Id [%s] does not belong to any fleet",
				instance.InstanceId, instance.FleetId, instance.ScalingGroupId)
		}(instance)
	}
	wg.Wait()
	// 更新当前实例数，保证仅有一个节点对当前实例数进行修改，降低并发冲突可能性
	updateCurNumToDB(log)
	return nil
}

// 巡查所有伸缩组的des和cur是否一致，不一致则向ecs查询
// 查询条件为：稳定条件的，未删除的。
// ecs返回的count与数据库的des相等，说明是因为某种原因有些实例未上报成功信息，不需要处理，不等则需要进行维护
func CorrectDesNumAndCurNum(log *logger.FMLogger, workNodewg *sync.WaitGroup) {
	defer workNodewg.Done()
	condition := orm.NewCondition()
	condition = condition.And("state", db.ScalingGroupStateStable)
	condition = condition.And("instance_type", common.InstanceTypeVM)
	condition = condition.And("is_deleted", "0")
	groupList, err := db.ScalingGroupTable().ListScalingGroupWithCond(condition)
	if err != nil {
		log.Error("[instance_monitor]list group error:%+v", err)
		return
	}
	for _, group := range groupList {
		if group.DesireInstanceNumber != group.CurrentInstanceNumber {
			curNumFromEcs, err := getCurNumFromEcs(&group, log)
			if err != nil {
				log.Error("[instance_monitor]get current instance number of group [%s] from ecs error:%+v", group.Id, err)
				return
			}
			deletingVmNum, _, _, err := db.EcsInfoTable().ListInstance(group.Id, db.EcsStateDeleting, 0, -1)
			if err != nil {
				log.Error("[instance_monitor] get deleting number of instance error:%+v, using default", err)
				deletingVmNum = defaultDeletingVmNum
			}
			curNumFromEcs -= int32(deletingVmNum)
			log.Info("[instance_monitor] instance from ecs:%d, group desire num:%d, group current num:%d",
				curNumFromEcs, group.DesireInstanceNumber, group.CurrentInstanceNumber)
			// 1. 扩容：实际ecs的数目少于期望
			if curNumFromEcs < group.DesireInstanceNumber {
				log.Info("[instance_monitor]group [%s] instance from ecs:%d < group desire num:%d, will scale out", group.Id, curNumFromEcs, group.DesireInstanceNumber)
				err = taskservice.StartEcsScalingOutTask(&group, group.DesireInstanceNumber)
			} else if curNumFromEcs > group.DesireInstanceNumber {
				// 2. 缩容：curNum > desireNum, 要判断活跃的实例数量
				if group.CurrentInstanceNumber > group.DesireInstanceNumber {
					log.Info("[instance_monitor]group [%s] instance current num:%d > group desire num:%d, will scale in", group.Id, group.CurrentInstanceNumber, group.DesireInstanceNumber)
					err = taskservice.StartEcsScaleInGroupTaskForRandomVms(&group, group.CurrentInstanceNumber-group.DesireInstanceNumber)
				}
			}
			if err != nil {
				log.Error("[instance_monitor]correcting current instance error:%+v", err)
				continue
			}
		}
	}
}

func updateCurNumToDB(log *logger.FMLogger) {
	condition := orm.NewCondition()
	condition = condition.And("state__in", db.ScalingGroupStateStable, db.ScalingGroupStateScaling)
	condition = condition.And("is_deleted", "0")
	groupList, err := db.ScalingGroupTable().ListScalingGroupWithCond(condition)
	if err != nil {
		log.Error("[instance_monitor] list group with error:%+v", err)
		return
	}
	for _, group := range groupList {
		if group.InstanceType == common.InstanceTypeVM {
			if err := db.UpdateCurrentNumber(group.Id, log); err != nil {
				log.Error("[instance_monitor] update group [%s] current instance num error:%+v", group.Id, err)
				continue
			}
		} else if group.InstanceType == common.InstanceTypePod {
			if err := db.UpdateCurrentPodsNumber(group.Id, log); err != nil {
				log.Error("[instance_monitor] update group [%s] current instance num error:%+v", group.Id, err)
				continue
			}
		}
	}
}

// 更新实例状态，先判断状态是否能够转换，再更新
func updateInstanceState(instance influxdb.InstanceInfo, log *logger.FMLogger) {
	instanceDB, err := db.EcsInfoTable().GetInstanceById(instance.InstanceId)
	if err != nil {
		log.Error("[instance_monitor] get instance [%s] from db err:%+v", instance.InstanceId, err)
		return
	}
	// 通过状态机判断是是否能转换状态
	if err = transferState(instanceDB.State, db.EcsStateActive); err != nil {
		log.Error("[instance_monitor] transfer instance [%s] state error:", err)
		return
	}
	// 如果是从activating -> active，更新激活时间
	if instanceDB.State == db.EcsStateActivating {
		if err := db.EcsInfoTable().UpdateActiveTime(instanceDB.InstanceId); err != nil {
			log.Error("update instance [%s] active time error:%+v", instanceDB.InstanceId, err)
			return
		}
	}
	// 乐观锁更新
	log.Info("[instance_monitor] instance [%s] is exist, update time", instance.InstanceId)
	if err := db.EcsInfoTable().UpdateStateByOCC(instance.InstanceId, db.EcsStateActive); err != nil {
		log.Error("[instance_monitor] update state err:%+v", err)
	}
	return
}

// 更新实例状态，先判断状态是否能够转换，再更新
func updatePodInstanceState(instance influxdb.InstanceInfo, log *logger.FMLogger) {
	podDB, err := db.PodInfoTable().GetPodByHostName(instance.InstanceId)
	if err != nil {
		log.Error("[instance_monitor] get instance [%s] from db err:%+v", instance.InstanceId, err)
		return
	}
	// 通过状态机判断是是否能转换状态
	if err = transferPosState(podDB.State, db.PodStateActive); err != nil {
		log.Error("[instance_monitor] transfer instance [%s] state error:", err)
		return
	}
	// 如果是从running -> active，更新激活时间
	if podDB.State == db.PodStateRunning {
		if err := db.PodInfoTable().UpdatePodActiveTime(podDB.HostName); err != nil {
			log.Error("update instance [%s] active time error:%+v", podDB.InstanceId, err)
			return
		}
	}
	// 乐观锁更新
	log.Info("[instance_monitor] pod [%s] is exist, update time", podDB.HostName)
	if err := db.PodInfoTable().TxUpdateState(podDB.HostName, db.PodStateActive); err != nil {
		log.Error("[instance_monitor] update state err:%+v", err)
	}
	return
}

// 如果是数据库没有的实例，可能是游离的，或者不是通过异步任务创建的，这类实例的信息不会有taskId，只要能正常上报都纳入管理
func insertNewInstance(instance influxdb.InstanceInfo, log *logger.FMLogger) {
	ecsInfo := &db.EcsInfo{
		InstanceId: instance.InstanceId,
		FleetId:    instance.FleetId,
		GroupId:    instance.ScalingGroupId,
		State:      db.EcsStateActive,
		TimeModel: db.TimeModel{
			IsDeleted: "0",
		},
	}
	ecsName, err := getInstanceName(instance, log)
	// 如果获取ecs名称失败，则在数据库中存自定义名称
	if err != nil {
		log.Error("[instance_monitor] get instance name error:%+v", err)
		ecsName = fmt.Sprintf("Fleet_%s_group_unknow", instance.FleetId)
	}
	ecsInfo.Name = ecsName

	if err := db.EcsInfoTable().Insert(ecsInfo); err != nil {
		log.Error("[instance_monitor] insert new instance err:%+v", err)
		return
	}
}

func getInstanceName(instance influxdb.InstanceInfo, log *logger.FMLogger) (string, error) {
	groupId := instance.ScalingGroupId
	group, err := db.ScalingGroupTable().GetScalingGroupById(groupId)
	if err != nil {
		log.Error("[instance_monitor] get scaling group [%s] error:%+v", groupId, err)
		return "", err
	}
	resCtrl, err := cloudresource.GetEcsController(group.ProjectId)
	if err != nil {
		log.Error("[instance_monitor] get ecs cloud resource of projectId[%s] error:%+v", group.ProjectId, err)
		return "", err
	}
	resp, err := resCtrl.ShowServer(instance.InstanceId)
	if err != nil {
		log.Error("[instance_monitor] show instance [%s] error:%+v", instance.InstanceId, err)
		return "", err
	}
	return resp.Server.Name, nil
}

func getCurNumFromEcs(group *db.ScalingGroup, log *logger.FMLogger) (int32, error) {
	// 从ecs查，多删少补
	resCtl, err := cloudresource.GetEcsController(group.ProjectId)
	if err != nil {
		return 0, err
	}
	ecsState := "ACTIVE"
	request := ecsmodel.ListServersDetailsRequest{
		Status: &ecsState,
		Name:   &group.FleetId,
	}
	resp, err := resCtl.ListServerDetail(&request)
	if err != nil {
		log.Error("[instance_monitor]list server detail error:%+v, request:%+v", err, request)
		return 0, err
	}
	curNumFromEcs := resp.Count
	return *curNumFromEcs, nil
}

func transferState(oldState, newState string) error {
	if oldState == newState {
		logger.R.Info("[instance_monitor] state %s is same, no need to transfer", oldState)
		return nil
	}
	switch newState {
	case db.EcsStateActive:
		return stateActiveCheck(oldState, newState)
	case db.EcsStateError:
		return stateErrorCheck(oldState, newState)
	case db.EcsStateDeleted:
		return stateDeletedCheck(oldState, newState)
	case db.EcsStateDeleting:
		return stateDeletingCheck(oldState, newState)
	}
	return fmt.Errorf("invalid state %s", newState)
}

func stateActiveCheck(oldState, newState string) error {
	if oldState == db.EcsStateError || oldState == db.EcsStateActivating {
		return nil
	}
	return fmt.Errorf("cannot change state from %s to %s", oldState, newState)
}

func stateErrorCheck(oldState, newState string) error {
	if oldState == db.EcsStateActive {
		return nil
	}
	return fmt.Errorf("cannot change state from %s to %s", oldState, newState)
}

func stateDeletingCheck(oldState, newState string) error {
	if oldState == db.EcsStateError || oldState == db.EcsStateActive {
		return nil
	}
	return fmt.Errorf("cannot change state from %s to %s", oldState, newState)
}

func stateDeletedCheck(oldState, newState string) error {
	if oldState == db.EcsStateError || oldState == db.EcsStateActive || oldState == db.EcsStateDeleting {
		return nil
	}
	return fmt.Errorf("cannot change state from %s to %s", oldState, newState)
}

// 只在后台巡检任务中进行的状态转换
// error, pending, running -> active，
// error, active, pending, running -> deleting
func transferPosState(oldState, newState string) error {
	if oldState == newState {
		logger.R.Info("[instance_monitor] state %s is same, no need to transfer", oldState)
		return nil
	}
	switch newState {
	case db.PodStateActive:
		return podStateActiveCheck(oldState, newState)
	case db.PodStateError:
		return podStateErrorCheck(oldState, newState)
	}
	return fmt.Errorf("invalid state %s", newState)
}

func podStateActiveCheck(oldState, newState string) error {
	if oldState == db.PodStateError || oldState == db.PodStateRunning || oldState == db.PodStatePending {
		return nil
	}
	return fmt.Errorf("cannot change state from %s to %s", oldState, newState)
}

func podStateErrorCheck(oldState, newState string) error {
	if oldState == db.PodStateError || oldState == db.PodStateActive || oldState == db.PodStateRunning || oldState == db.PodStatePending {
		return nil
	}
	return fmt.Errorf("cannot change state from %s to %s", oldState, newState)
}
